#pragma once
#include "util.hpp"
#include <sstream>
#include <iostream>
#include <fstream>
#include <atomic>
#include <memory>
#include <ctime>
#include <cstddef>

namespace bit
{
    class LogSink
    {
    protected:
        //保护构造函数，限制其直接实例化：
        LogSink() = default;
    public:
        using ptr=std::shared_ptr<LogSink>;
        virtual ~LogSink() {}
        virtual void log(const char *data, size_t len) = 0;
    };

    // 落地方向（一）：标准输出即显示器
    class StdoutSink : public LogSink
    {
    private:
        //将派生类的构造函数私有化，禁止用户跳过工厂本身直接创建对象：
        StdoutSink() = default;
        friend class SinkFactory;
    public:
        // len的单位是Byte
        void log(const char *data, size_t len) override
        {
            std::cout.write(data, len);
        }
    };
    // 落地方向（二）：指定的文件
    class FileSink : public LogSink
    {
    private:
        /* RAII风格 */
        // 构造函数：
        FileSink(const std::string &filename) : _filename(filename)
        {
            // 1.检查路径的有效性，若路径无效，则需要创建路径：
            const std::string parent_path = util::get_path(filename);
            if (!util::exists(parent_path))
            {
                util::create_path(parent_path);
            }

            // 2.来到这里说明路径一定存在，所以只需要创建并打开这个文件即可：
            // 以文本模式进行写入，有效处理不同平台下的\n
            _ofs.open(filename, std::ios_base::app);
            if (!_ofs.is_open())
            {
                std::cout << "指定的文件打开失败" << std::endl;
                abort();
            }
        }
        friend class SinkFactory;
    public:
        void log(const char *data, size_t len) override
        {
            _ofs.write(data, len);
        }
        // 析构函数
        ~FileSink() override
        {
            _ofs.close();
        }

    private:
        std::ofstream _ofs;
        std::string _filename;
    };
    // 落地方向（三）：设置滚动文件
    class RollFileSink : public LogSink
    {
    private:
        // 构造函数：
        // (1)参数一告诉系统，我想在哪里保存我的日志文件；（2）参数二告诉系统：我的日志系统的命名风格是啥：
        RollFileSink(size_t maxSize = 1024 * 1024, const std::string &pathname = "./LogMk/",
                     // 默认的文件大小是1MB，内容超过1MB（包括1MB）的文件将被滚动
                     const std::string &basename = "log.txt")
            : _pathname(pathname), _basename(basename), _maxSize(maxSize), _curSize(0)
        {
            // 1.创建路径：
            if (_UpdateTime())
            {
                _curpath = _pathname;
                // 条件编译：
                //Windows平台走这里：(32位和64位环境都定义了_WIN32)
                #ifdef _WIN32
                if (_pathname[_pathname.size() - 1] != '\\')
                    _curpath.push_back('\\');
                #endif

                //Linux平台走这里：
                #ifdef __linux__
                if (_pathname[_pathname.size() - 1] != '/')
                    _curpath.push_back('/');
                #endif

                _curpath += (std::to_string(ptime->tm_year + 1900) + '-' +
                        std::to_string(ptime->tm_mon + 1) + '-' +
                        std::to_string(ptime->tm_mday));
                util::create_path(_curpath);
            }

            // 2.创建并打开一个文件：
            _curofs.open(_CreateNewFile(), std::ios_base::out);
            if (!_curofs.is_open())
            {
                std::cout << "创建滚动日志文件失败" << std::endl;
                abort();
            }
        }
        friend class SinkFactory;
    public:
        //重载log函数：
        void log(const char *data, size_t len) override
        {
            _curofs.write(data, len);
            _curSize += len;
            if(_curSize >= _maxSize)
            {
                //1.需要关闭之前的文件：
                _curofs.close();

                //2.需要把_curSize置空：
                _curSize = 0;

                //3.创建一个新的文件，并让_curofs维护这个文件：
                _curofs.open(_CreateNewFile(), std::ios_base::out);
            }
        }

        //析构函数：
        ~RollFileSink() override
        {
            _curofs.close();
        }
    private:
        //创建新文件：形成类似于log_1.txt，log_2.txt...这样的组合
        const std::string _CreateNewFile()
        {
            std::stringstream ss;
            size_t pos = _basename.find_last_of(".");
            if(pos == std::string::npos)
            {
                std::cout << "请使用带后缀的basename（基类文件名）。"<<std::endl;
                abort();
            }
            #ifdef _WIN32
            ss << _curpath + '\\';
            #endif

            #ifdef __linux__
            ss << _curpath + '/';
            #endif
            ss << _basename.substr(0, pos + 1);
            ss << "_";
            ss << std::to_string(count);
            ss << _basename.substr(pos + 1);
            count++;

            return ss.str();
        }
        // 静态的成员函数：如果为真，就需要新建目录，为假不需要新建目录
        static bool _UpdateTime()
        {
            if (ptime != nullptr)
            {
                // 1.获取当前的时间：
                int year, month, day;
                sscanf(util::get_curtime().c_str(), "%d-%d-%d", &year, &month, &day);
                if (((year - 1900) == ptime->tm_year) &&
                    ((month - 1) == ptime->tm_mon) &&
                    (day == ptime->tm_mday))
                {
                    return false;
                }
            }
            else
            {
                //TODO：有一个内存问题的考虑（避免内存泄漏）
                ptime = new struct tm;
            }

            time_t now = time(nullptr);
            //重点且易错：这里的ptime不能为空：
            localtime_r(&now, ptime);

            //新目录建立后，滚动文件的序号就要从头开始了：
            count = 1;
            return true;
        }
    private:
        std::string _pathname;
        std::string _curpath;   //当前的文件路径 = _pathname + 打印日志的时间（以天为单位）
        std::string _basename;

        std::ofstream _curofs;

        //存在：多个日志器如何协调的问题（单个对象肯定没啥问题）
        size_t _maxSize; // 滚动文件的最大大小；
        size_t _curSize; // 当前滚动文件的大小；

        // 类内声明：
        static struct tm* ptime;//记录当前时间（以"天"为单位进行记录）
        static size_t count;    //滚动文件序号
    };
    // 类外初始化：
    struct tm *RollFileSink::ptime = nullptr;
    size_t RollFileSink::count = 1;

    //工厂类：
    class SinkFactory
    {
    public:
        template<typename Sink, typename...Args>
        static LogSink::ptr create(Args...args)
        {
            // return std::make_shared<Sink>(std::forward<Args>(args)...);
            return std::shared_ptr<LogSink>(new Sink(std::forward<Args>(args)...));
        }
    };
}